﻿using Alachisoft.NCache.Caching.Distributed;
using Dapper;
using Furion;
using Furion.DatabaseAccessor;
using FurStudy.Core;
using Mapster;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using MongoDB.Driver;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FurStudy.EntityFramework.Core
{
    [AppStartup(600)]
    public sealed class FurEntityFrameworkCoreStartup : AppStartup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            // 配置数据库上下文，支持N个数据库
            services.AddDatabaseAccessor(options =>
            {
                // 配置默认数据库
                options.AddDbPool<DefaultDbContext>();

                // 配置多个数据库，多个数据库必须指定数据库上下文定位器
                // options.AddDbPool<MySQLDbContext, MySQLDbContextLocaotr>();

                // 配置默认数据库
                options.AddDbPool<FurionDbContext>(DbProvider.MySql, connectionMetadata: "连接字符串");

                // 【新版MYSQL注意】 MySQL在新版本中注册有所更改，所以注册方式为：
                options.AddDbPool<FurionDbContext>($"{DbProvider.MySql}@8.0.22");

                // 配置多个数据库，多个数据库必须指定数据库上下文定位器
                // options.AddDbPool<MySQLDbContext, MySQLDbContextLocaotr>(DbProvider.MySql, connectionMetadata: "连接字符串");

                // 如果有多个数据库操作，那么从第二个起，就需要绑定数据库上下文定位器

                // 注册数据库上下文和定位器
                //options.RegisterDbContext<FurionDbContext, FurionDbContextLocator>();

                // 获取连接字符串
                var connStr = DbProvider.GetConnectionString<FurionDbContext>( /* 这里可写可不写 */ );

                // 使用原生注册
                options.AddDbContextPool<FurionDbContext>(builder =>
                {
                    // 注册特定数据库
                    builder.UseSqlite(connStr, dbContextOptions =>
                     {
                         // 配置迁移程序集
                         dbContextOptions.MigrationsAssembly("迁移程序集");
                     }).EnableSensitiveDataLogging() // 启动详细错误
                    .EnableSensitiveDataLogging() // 替换参数为具体参数值
                    .AddInterceptors(DbProvider.GetDefaultInterceptors()); // 添加默认过滤器
                }, 100); // 配置数据库连接池


                // 注册并配置服务Zack.EFCore.Batch
                options.AddDbPool<DefaultDbContext>(providerName: default, optionBuilder: opt =>
                {
                    opt.UseBatchEF_MySQLPomelo(); // MySQL数据库包
                });

                // 注册主从库数据库上下文
                options.AddDbPool<MasterDbContext>();
                options.AddDbPool<SlaveDbContext, SlaveDbContextLocator>();

                // ====================SaaS多租户====================
                // 基于TenantId的方式
                // 注册多租户数据库上下文
                // 此方式在中小型企业系统中最为常用，维护成本低，购置成本低
                options.AddDbPool<MultiTenantDbContext, FurStudy.Core.MultiTenantDbContextLocator>();

                // 基于Database的方式
                // 注册多租户数据库上下文
                // 此方式在中大型企业系统中最为常用，一个租户（客户）一个独立的数据库
                // 需要Database多租户方案的数据库上下文需要采用AddDb注册，而不是AddDbPool。原因是AddDbPool方式注册后续不支持OnConfiguring重写！！！
                options.AddDb<FurionDbContext>();
                options.AddDbPool<MultiTenantDbContext, FurStudy.Core.MultiTenantDbContextLocator>();

                // 启用自定义多租户类型配置
                options.CustomizeMultiTenants(); // 启用自定义多租户类型，有一个默认参数，配置多租户表字段名
                options.AddDbPool<FurionDbContext>();
            });

            #region 注册SqlSugar服务

            // =====配置单库=====
            services.AddSqlSugar(new ConnectionConfig
            {
                ConnectionString = "Server=.xxxxx", // 连接符字串
                DbType = DbType.SqlServer,
                IsAutoCloseConnection = true,
                InitKeyType = InitKeyType.Attribute // 从特性读取主键自增信息
            });
            // =====配置多库=====
            List<ConnectionConfig> connectConfigList = new List<ConnectionConfig>();
            // 数据库1
            connectConfigList.Add(new ConnectionConfig
            {
                ConnectionString = "链接字符串1",
                DbType = DbType.MySql,
                IsAutoCloseConnection = true,
                InitKeyType = InitKeyType.Attribute,
                ConfigId = "0",
                AopEvents = new AopEvents
                {
                    // 多库状态下每个库必须单独绑定打印事件，否则只会打印第一个库的sql日志
                    OnLogExecuting = (sql, pars) =>
                    {
                        Console.WriteLine(SqlProfiler.ParameterFormat(sql, pars));
                        Console.WriteLine();
                    }
                }
            });
            //数据库2
            connectConfigList.Add(new ConnectionConfig
            {
                ConnectionString = "链接字符串2",
                DbType = DbType.MySql,
                IsAutoCloseConnection = true,
                InitKeyType = InitKeyType.Attribute,
                ConfigId = "1",
                AopEvents = new AopEvents
                {
                    //多库状态下每个库必须单独绑定打印事件，否则只会打印第一个库的sql日志
                    OnLogExecuting = (sql, pars) =>
                    {
                        Console.WriteLine(SqlProfiler.ParameterFormat(sql, pars));
                        Console.WriteLine();
                    }
                }
            });
            services.AddSqlSugar(connectConfigList.ToArray());

            // 同时也可以添加更多配置，如： ps：多库状态下每个库必须单独绑定打印事件，否则只会打印第一个库的sql日志（参考上面的多库配置）
            services.AddSqlSugar(connectConfigList.ToArray(),
            db =>
            {
                //处理日志事务
                db.Aop.OnLogExecuting = (sql, pars) =>
                {
                    Console.WriteLine(sql);
                    Console.WriteLine(string.Join(",", pars?.Select(it => it.ParameterName + ":" + it.Value)));
                    Console.WriteLine();
                    App.PrintToMiniProfiler("SqlSugar", "Info", sql + "\r\n" + db.Utilities.SerializeObject(pars.ToDictionary(it => it.ParameterName, it => it.Value)));

                    // 打印sql到swagger
                    // 方法一
                    App.PrintToMiniProfiler("SqlSugar", "Info", sql + "\r\n" + string.Join(",", pars?.Select(it => it.ParameterName + ":" + it.Value)));

                    // 方法二:Furion对上述用法进行二次封装 建议方式 Sql参数会直接写入到生成Sql中
                    App.PrintToMiniProfiler("SqlSugar", "Info", SqlProfiler.ParameterFormat(sql, pars));
                };
            });

            #endregion

            #region 注册Dapper服务

            services.AddDapper("Data Source=./Furion.db", SqlProvider.SqlServer);

            #endregion

            #region 注册MongoDB服务

            // 方式一
            services.AddMongoDB("mongodb://localhost:27017");
            // 方式二
            services.AddMongoDB(new MongoClientSettings { });
            // 方式三
            services.AddMongoDB(new MongoUrl("mongodb://localhost:27017") { });

            #endregion

            #region 自定义高级注册

            // Furion框架中的AppDbContext数据库上下文还有ISqlDispatchProxy都是通过这种方式创建的

            //services.AddScoped(typeof(ISpecService), provider = > {
            //    // 自定义任何创建实例的方式
            //    var instance = new SpecService();  // 或者可以通过AOP插件返回代理实例

            //    return instance;
            //});

            #endregion

            #region 对象数据映射Mapper-全局默认配置

            TypeAdapterConfig.GlobalSettings.Default.PreserveReference(true);

            #endregion

            #region 注册分布式SQL Server缓存

            services.AddDistributedSqlServerCache(options => 
            {
                options.ConnectionString = "DistCache_ConnectionString"; // _config["DistCache_ConnectionString"];
                options.SchemaName = "dbo";
                options.TableName = "TestCache";
            });

            #endregion

            #region 注册分布式Redis缓存

            services.AddStackExchangeRedisCache(options => 
            {
                // 连接字符串，这里也可以读取配置文件
                options.Configuration = "192.168.111.134,password=aW1HAyupRKmiZn3Q";
                // 键名前缀
                options.InstanceName = "furion_";
            });

            #endregion

            #region 注册分布式NCache缓存

            services.AddNCacheDistributedCache(configuration => 
            {
                configuration.CacheName = "demoClusteredCache";
                configuration.EnableLogs = true;
                configuration.ExceptionsEnabled = true;
            });

            #endregion
        }
    }
}
